package ferp.core.ai.strategy.simple;

import ferp.core.ai.strategy.Dropping;
import ferp.core.card.Card;
import ferp.core.game.Bid;
import ferp.core.game.Game;
import ferp.core.game.Settings;
import ferp.core.log.Log;
import ferp.core.player.AI;
import ferp.core.player.Hand;

import java.util.ArrayList;
import java.util.Map;
import java.util.TreeMap;

/**
 * User: igorgok
 * Date: 10/30/11 10:16 AM
 */
public class SimpleDropping extends Dropping
{
  @Override
  public void tricks(int hash, boolean isFirstHand, int sets, int[] drop)
  {
    // trump is not known here yet
    int pair = tricking.execute(hash, Estimate.trump(hash, isFirstHand), isFirstHand, 0, sets, 0);

    drop[0] = Hand.first(pair);
    drop[1] = Hand.last(pair);
  }

  @Override
  public void misere(Game g, AI ai)
  {
    // the misere is sure, so we always drop the talon
    for (int i = 0; i < Game.TALON_SIZE; ++i)
      g.setDrop(i, g.getTalonHash(i));
  }

  @Override
  public boolean isWithout3(Game g, AI ai, Settings settings)
  {
    int[] drop = new int[Game.TALON_SIZE];
    int cards = g.getHand(ai.id()).current;

    // simulate drop for estimating the contract
    tricks(cards, ai.isFirstHand(g), g.sets, drop);
    cards = Hand.remove(cards, drop);

    Bid estimated = ai.strategy().contracting().tricks(cards, ai.isFirstHand(g));

    Log.debug(Log.TAG, "estimated bid for calculating without3: " + estimated);

    return Simulate.without3(g, settings, ai, g.bid, estimated) > Simulate.play(g, settings, ai, g.bid, estimated);
  }

  // calculate tricking drop considering already trashed cards
  public int tricks(int hash, Card.Suit trump, boolean isFirstHand, int trash, int sets, int included)
  {
    return tricking.execute(hash, trump, isFirstHand, trash, sets, included);
  }

  // calculate misere drop considering already trashed cards
  public int misere(int hash, Card.Suit trump, boolean isFirstHand, int trash, int sets, int included)
  {
    return misere.execute(hash, trump, isFirstHand, trash, sets, included);
  }

  private static final int MAX_GROUPS = Game.MAXIMUM_ROUNDS + 1;
  private static final int MAX_TRICKS_10000 = Game.MAXIMUM_TRICKS * 10000;
  private static final int ONE_TRICK_10000 = 10000;

  private static abstract class Evaluator
  {
    public int execute(int hash, Card.Suit trump, boolean isFirstHand, int trash, int sets, int included)
    {
      initialize(hash, trump, isFirstHand);

      int g = 0;

      for (Map.Entry<Integer, ArrayList<Integer>> e : groups.entrySet())
      {
        ArrayList<Integer> group = e.getValue();

        accepted.clear();

        for (int pair : group)
          // in case we have an "included" card make sure it's included in the pair
          if ((pair & trash) == 0 && (pair | included) == pair)
            accepted.add(pair);

        if (accepted.size() > 0)
        {
          // choose drop "randomly" e.g. based on the number of sets played in the current game to mislead the human player
          int pair = accepted.get(sets % accepted.size());

          Log.debug(Log.TAG, "drop group #" + g + " with delta " + e.getKey() + " of size " + accepted.size() + "; selected index " + sets % accepted.size() + ", cards " + Hand.dump(pair));

          return pair;
        }

        ++g;
      }

      // should never get here
      return 0;
    }

    protected abstract int evaluate(int hash, Card.Suit trump, boolean isFirstHand, int c1, int c2);

    // drop groups
    private final TreeMap<Integer, ArrayList<Integer>> groups = new TreeMap<Integer, ArrayList<Integer>>();
    // cached hash
    private int hash;
    // accepted entries
    private final ArrayList<Integer> accepted = new ArrayList<Integer>();

    private void initialize(int hash, Card.Suit trump, boolean isFirstHand)
    {
      // do not initialize if already initialized for this hash
      if (this.hash == hash)
        return;

      int h = this.hash = hash;

      // clear the groups first
      groups.clear();

      // create all possible drops (total of 12*11/2 = 66) dividing them into groups based on tricks estimation
      // each group may contain more than one drop. All drops in such a group are equally priced.
      for (int i = 0; i < MAX_GROUPS; ++i)
      {
        int c1 = Hand.first(h);

        h = Hand.remove(h, c1);

        for (int c2 = Hand.first(h); c2 != 0; c2 = Hand.next(h, c2))
        {
          int delta = evaluate(hash, trump, isFirstHand, c1, c2);

          ArrayList<Integer> group = groups.get(delta);

          if (group == null)
          {
            group = new ArrayList<Integer>();

            groups.put(delta, group);
          }

          group.add(c1 | c2);
        }
      }
    }
  }

  private static final Evaluator tricking = new Evaluator()
  {
    @Override
    protected int evaluate(int hash, Card.Suit trump, boolean isFirstHand, int c1, int c2)
    {
      int t10000 = Estimate.tricks(Hand.remove(hash, c1 | c2), trump, isFirstHand, bid).t10000;

      //Log.debug(Log.TAG, "drop: " + Card.card(c1) + Card.card(c2) + ", hand: " + Hand.dump(Hand.remove(hash, c1 | c2)) + ", t10000=" + t10000);

      // use trump to check the drop and adjust the estimation, assuming one trump card always equals one trick
      if (Card.card(c1).suit == trump)
        t10000 -= ONE_TRICK_10000;

      if (Card.card(c2).suit == trump)
        t10000 -= ONE_TRICK_10000;

      return MAX_TRICKS_10000 - t10000;
    }

    private final Bid bid = Bid.play();
  };

  private static final Evaluator misere = new Evaluator()
  {
    @Override
    protected int evaluate(int hash, Card.Suit trump, boolean isFirstHand, int c1, int c2)
    {
      return Estimate.misere(Hand.remove(hash, c1 | c2));
    }
  };

    // debug output
/*
    Set<TreeMap.Entry<Integer, ArrayList<Integer>>> entries = groups.entrySet();
    int pairs = 0;

    for (SortedMap.Entry<Integer, ArrayList<Integer>> g : entries)
      pairs += g.getValue().size();

    Log.debug(Log.TAG, "drop groups " + groups.size() + ", drop pairs " + pairs);

    for (SortedMap.Entry<Integer, ArrayList<Integer>> g : entries)
    {
      StringBuilder sb = (new StringBuilder(512)).append("delta ").append(g.getKey()).append(": ");

      for (int pair : g.getValue())
        sb.append(Card.card(Hand.first(pair)).toString()).append(Card.card(Hand.last(pair)).toString()).append(' ');

      Log.debug(Log.TAG, sb.toString());
    }
*/
}
